Extend drag-and-drop to nested blocks via the content model#19
Merged
Conversation
Every %block element now carries its own drag handle wherever the model places blocks (a region, a cm:flow container or a blockquote) - lifting a block out of a list item, quote or cell, or dropping one in, resolves to the innermost legal level (local:deepest-block-at + local:legal-drop-level; illegal drops clamp to the nearest legal ancestor, the gutter lifts out to top level). The vacated origin is repaired at commit (prune-husks + collapse-container), local:ensure-chrome converges handles after every mutation and undo restore, and transient drag state is stripped before the undo snapshot (local:tidy-class kills class="" residue). Handles stack per nesting level; the editing view indents blockquote content so quote-child handles get their own gutter column. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nothing can legally drop before or after a td/th, so a cell's pixels are unambiguous: pointing into a cell away from any block it holds now drops the dragged block INTO it (the text host becomes a container - runs wrapped, block appended - per the insert-block-at-caret flow doctrine), marked by an inset drop-into box instead of a before/after line. Hovering a block inside a cell still places before/after that block. List items deliberately keep clamping to around-the-list: their pixels compete with reordering, and Tab/indent already move content into items. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The help modal's Reordering section, the demo's Try-it list (the quote's inner paragraph now demonstrates a nested handle; cells take drops) and the README feature list all described the old top-level-only handles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The editable-region convention bullet still said init injects chrome top-level only; also name the into-cell drop mode in the dragnest suite description. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A normalized blockquote > p reads as a plain paragraph without a visual clue; the Markdown/Bootstrap-style left border (with muted text) makes the container visible, and the slightly wider padding keeps the inner blocks' drag handles clear of the bar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Every
%blockelement now carries its own drag handle wherever the XHTML content model places blocks — a region, acm:flowcontainer (li/dd/td/th/div/figure/figcaption) or ablockquote— so nested blocks (including an RDFa objectdivin a list item) can be dragged out of, into and between containers.local:draggable-block) routes every chrome/drag decision: a real%block— never a run wrapper,hror ephemera. Chrome injection rides the existinglocal:init-blockrecursion (a one-gate change);local:ensure-chromeinafter-mutationconverges handles after every gesture, so mutation sites never thread their own injection.local:deepest-block-at+local:legal-drop-levelclimbing to the nearest legal level — the region accepts any block, so the climb terminates and an illegal drop is clamped, never created). Gutter/gap drops stay top-level, so the left gutter lifts a nested block all the way out.td/th, so a cell's pixels away from any block it holds mean into it — the text host becomes a container per theinsert-block-at-caretflow doctrine, marked by an insetdrop-intobox. A list item deliberately stays a clamp to around-the-list (its pixels compete with reordering).prune-husks(an emptied quote goes — B7 doctrine) thencollapse-container(a flow container that lost its last block reverts to a text host).local:tidy-classkills the latentclass=""residueclassList.removeleft behind (previously poisoned undo baselines after any completed drag);restore-snapshotstrips every handle and re-converges, generalizing the table-fostering fix to nested tables.blockquotecontent (plus a Markdown-style quote bar) so quote-child handles don't coincide with the quote's.Test plan
make test— 49 headless PASS, incl. new canonical (nested-chrome strip at any depth) and lint (nested object-div shapes clean) fixturesmake test-browser— all 15 suites green, incl. newdragnest.mjs(real-mouse gestures againsttests/fixture-dragnest.html: nested handles at init, lift-out of a list item — incl. the RDFa object-div case with triples intact — drop-into a container and INTO a text-only cell, before/after precision on a cell's blocks, legality clamp, region clamp, origin collapse, split convergence, canonical cleanliness; every scenario ends in invariants + single-undo byte-exact restore)noChromeOnP,noChromeInCell, …) deliberately flipped🤖 Generated with Claude Code